package edu.tamu.cse.teamproject1.Database.Parser;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import edu.tamu.cse.teamproject1.Database.ColumnDefinition;
import edu.tamu.cse.teamproject1.Database.EnumColumnType;
import edu.tamu.cse.teamproject1.Database.Table;
import edu.tamu.cse.teamproject1.Database.Tuple;
import edu.tamu.cse.teamproject1.Database.Exceptions.NullReferenceException;
import edu.tamu.cse.teamproject1.Database.Exceptions.SqlException;
import edu.tamu.cse.teamproject1.Database.Interfaces.IColumnDefinition;
import edu.tamu.cse.teamproject1.Database.Interfaces.IDatabaseTable;
import edu.tamu.cse.teamproject1.Database.Interfaces.ITuple;
import edu.tamu.cse.teamproject1.Database.Interfaces.ITupleElement;

/**
 * Operational Class to Parse Queries Against DatabaseTables
 * 
 * The QueryParser class will perform operations on a database table to retrieve,
 * update, or delete data in the table based on the query passed to the parser
 * object. 
 * 
 * Because of the complexity of SQL queries, this parser was created to handle
 * the variety of requests. It is capable of handling SELECT, UPDATE, and DELETE
 * queries (INSERT doesn't need to be parsed because it doesn't have filters). 
 * On each of SELECT, UPDATE, and DELETE, a filter may be applied (making use of
 * the {@link QueryFilter} class) to limit the result set that should be operated
 * on. 
 * 
 * @author Christopher Weldon <chris@chrisweldon.net>
 * @since Release 0.1.0
 */
public class QueryParser {
	
	protected List<IDatabaseTable> _tables; 
	
	/**
	 * 
	 * This counts the number of rows in a table (excepted
	 * 
	 * @param tableCounting
	 * @param aliasName
	 * @return a table with one column and one row containing the count of rows
	 * from the table tableCounted
	 */
	IDatabaseTable CountTable(IDatabaseTable tableCounting, String aliasName){
		int count = 0;
		boolean countFlag;
		String tableName = tableCounting.getName();
		
		/*loop through the table and find all the tuples 
		 * with no null values and increment the count*/
		for(ITuple t: tableCounting.getAllTuples()){
			countFlag = true;
			
			for(ITupleElement e: t.getAllElements()){
				if(e.getValue().equals(null)){
					countFlag = false;
					break;
				}
			}
			
			if(countFlag) count++;
		}
		
		/*create a new table with one column and tuple with the count as it's value*/
		IDatabaseTable generatedTable = new Table(tableName);
		IColumnDefinition countColumn = new ColumnDefinition(EnumColumnType.integer, aliasName);
		ITuple countTuple = new Tuple();
		ITupleElement countTupleElement = countColumn.createTupleElement(count, aliasName);
		countTuple.addTupleElement(countTupleElement);
		
		generatedTable.addColumn(countColumn);
		generatedTable.addTuple(countTuple);
		
		return generatedTable;
	}
	
	/**
	 * 
	 * This sums the values in a table row and returns a single row with this sum
	 * 
	 * @param tableCounting
	 * @param origColumnName
	 * @param aliasName
	 * @return a table with one column and one row containing the count of rows
	 * from the table tableCounted
	 */
	IDatabaseTable SumTable(IDatabaseTable tableCounting, String origColumnName, String aliasName) throws Exception {
		// Figure out the type
		EnumColumnType type = null;
		for (IColumnDefinition def : tableCounting.getColumnLists()) {
			if (def.getColumnName().equals(origColumnName)) {
				type = def.getColumnType();
				break;
			}
		}
		
		if (type == null) {
			throw new Exception("origColumnName not defined.");
		}
		
		if (type != EnumColumnType.integer && type != EnumColumnType.floating) {
			throw new Exception("Cannot perform sum on columns other than integer or floating.");
		}
		
		Float sum = 0.0F;
		String tableName = tableCounting.getName();
		
		/*loop through the table and find all the tuples 
		 * with no null values and increment the count*/
		for(ITuple t: tableCounting.getAllTuples()){
			for(ITupleElement e: t.getAllElements()){
				if (!e.getColumnName().equals(origColumnName)) {
					continue;
				}
				if(!e.getValue().equals(null)) {
					sum += (Float)e.getValue();
				}
				break;
			}
		}
		
		/*create a new table with one column and tuple with the count as it's value*/
		IDatabaseTable generatedTable = new Table(tableName);
		IColumnDefinition countColumn = new ColumnDefinition(type, aliasName);
		ITuple countTuple = new Tuple();
		ITupleElement countTupleElement = null;
		if (type == EnumColumnType.integer) {
			countTupleElement = countColumn.createTupleElement(sum.intValue(), aliasName);
		} else if (type == EnumColumnType.floating) {
			countTupleElement = countColumn.createTupleElement(sum, aliasName);
		}
		countTuple.addTupleElement(countTupleElement);
		generatedTable.addColumn(countColumn);
		generatedTable.addTuple(countTuple);
		
		return generatedTable;
	}
	
	/**
	 * finds the table's tuple with the minimum value according to a certain column and
	 * returns the table with only that tuple
	 * @param tableMin
	 * @param origColumnName
	 * @param columnAlias
	 * @return a table with one tuple
	 */
	IDatabaseTable TableMinColumn(IDatabaseTable tableMin, String origColumnName, String columnAlias) {
		boolean tupleFlag;
		int i = 0;
		
		if(tableMin == null) return null;
		
		if (tableMin.getAllTuples().size() == 0) {
			// Simply return the table since no parsing can be done
			return tableMin;
		}
		
		ITuple minTuple = tableMin.getAllTuples().get(0);
		
		/*loop through the table and find all the tuples 
		 * with no null values and increment the count*/
		for(ITuple t: tableMin.getAllTuples()){
			/*check to see if the tuples and the comlumn definitions agree*/
			if(minTuple.getAllElements().size() != tableMin.getColumnLists().size())
				throw new IllegalStateException("The table view's columns do not match the tuples");
			
			i = 0;
			tupleFlag = false;
			/*loop through the each tuple element to find which should be compared to 
			 * the current minimum value*/
			for(ITupleElement e: t.getAllElements()){
				
				if(e.getColumnName().equals(origColumnName)){
					/*compare the minimum value according to the type*/
					switch(e.getColumnType()){
						case integer:
							Integer intMin = (Integer)minTuple.getAllElements().get(i).getValue();
							Integer intElem = (Integer)e.getValue();
							if(intElem < intMin){
								minTuple = t;
							}
							break;
						case date:
							Date dateMin = (Date)minTuple.getAllElements().get(i).getValue();
							Date dateElem = (Date)e.getValue();
							if(dateElem.before(dateMin)){
								minTuple = t;
							}
							break;
						case varchar:
							throw new IllegalStateException("Cannot Compare Strings");
						case floating:
							Float floatMin = (Float)minTuple.getAllElements().get(i).getValue();
							Float floatElem = (Float)e.getValue();
							if(floatElem < floatMin){
								minTuple = t;
							}							
							break;
					}
					
				}
				
				if(tupleFlag) break;
				
				i++;
			}
			
		}
		
		// Update the column name
		for (IColumnDefinition def : tableMin.getColumnLists()) {
			 if (def.getColumnName().equals(origColumnName)) {
				 def.setColumnName(columnAlias);
				 break;
			 }
		}
		// Update the column name on the new tuple
		for (ITupleElement elem : minTuple.getAllElements()) {
			if (elem.getColumnName().equals(origColumnName)) {
				elem.setColumnName(columnAlias);
				break;
			}
		}
		/*delete the tables tuples and add in the new tuple*/
		tableMin.clearTuples();
		tableMin.addTuple(minTuple);
		
		return tableMin;	
	}
	
	/**
	 * searches a table's tuples for the maximum tuple according to a certain column and
	 * returns the table with only that tuple
	 * @param tableMax
	 * @param origColumnName
	 * @param columnAlias
	 * @return the table with one tuple
	 */
	IDatabaseTable TableMaxColumn(IDatabaseTable tableMax, String origColumnName, String columnAlias) {
		boolean tupleFlag;
		int i = 0;
		
		if(tableMax == null) return null;
		
		if (tableMax.getAllTuples().size() == 0) {
			// Simply return the table since we have no parsing to do
			return tableMax;
		}
		
		ITuple maxTuple = tableMax.getAllTuples().get(0);
		
		/*loop through the table and find all the tuples 
		 * with no null values and increment the count*/
		for(ITuple t: tableMax.getAllTuples()){
			/*check to see if the tuples and the comlumn definitions agree*/
			if(maxTuple.getAllElements().size() != tableMax.getColumnLists().size())
				throw new IllegalStateException("The table view's columns do not match the tuples");
			
			i = 0;
			tupleFlag = false;
			/*loop through the each tuple element to find which should be compared to 
			 * the current maximum value*/
			for(ITupleElement e: t.getAllElements()){
				
				if (e.getColumnName().equals(origColumnName)){
					/*compare the maximum value according to the type*/
					switch(e.getColumnType()){
						case integer:
							Integer intMax = (Integer)maxTuple.getAllElements().get(i).getValue();
							Integer intElem = (Integer)e.getValue();
							if(intElem > intMax){
								maxTuple = t;
							}
							break;
						case date:
							Date dateMax = (Date)maxTuple.getAllElements().get(i).getValue();
							Date dateElem = (Date)e.getValue();
							if(dateElem.after(dateMax)){
								maxTuple = t;
							}
							break;
						case varchar:
							throw new IllegalStateException("Cannot Compare Strings");
						case floating:
							Float floatMax = (Float)maxTuple.getAllElements().get(i).getValue();
							Float floatElem = (Float)e.getValue();
							if(floatElem > floatMax){
								maxTuple = t;
							}							
							break;
					}
					
				}
				
				if(tupleFlag) break;
				
				i++;
			}
			
		}
		
		// Update the column name
		for (IColumnDefinition def : tableMax.getColumnLists()) {
			 if (def.getColumnName().equals(origColumnName)) {
				 def.setColumnName(columnAlias);
				 break;
			 }
		}
		// Update the column name on the new tuple
		for (ITupleElement elem : maxTuple.getAllElements()) {
			if (elem.getColumnName().equals(origColumnName)) {
				elem.setColumnName(columnAlias);
				break;
			}
		}
		/*delete the tables tuples and add in the new tuple*/
		tableMax.clearTuples();
		tableMax.addTuple(maxTuple);
		
		return tableMax;	
	}
	
	/**
	 * Creates a joined table from a list of database tables
	 * 
	 * @param tables
	 * @return A joined table
	 */
	protected IDatabaseTable createJoinedTable(List<IDatabaseTable> tables) {
		// If we have only one table, there's not point in doing this processing. Return the table. 
		if (tables.size() == 1) {
			return tables.get(0);
		}
		
		// Get the table names to create a pseudo-unique join table name
		String newTableName = "";
		for (IDatabaseTable table : tables) {
			if (newTableName != "") {
				// Hyphen to separate table names
				newTableName += "-";
			}
			newTableName += table.getName();
		}
		
		// Create the table with columns
		// CAUTION: O(n^2)
		IDatabaseTable joinTable = new Table(newTableName);
		// Set that this is a view
		joinTable.setIsView(true);
		for (IDatabaseTable table : tables) {
			// Get the columns and add to the join table
			for (IColumnDefinition def : table.getColumnLists()) {
				IColumnDefinition cloneDef = (IColumnDefinition)def.clone();
				cloneDef.setColumnName(table.getName() + "." + cloneDef.getColumnName());
				joinTable.addColumn(cloneDef);
			}
		}
		/*
		 * We must clone all the tuple elements to prevent original in-memory 
		 * modifications of any objects. Thus, fetch all the tuple elements, deep
		 * copy them, and then change the column name.
		 */
		for (IDatabaseTable table : tables) {
			// Add the tuples
			for (ITuple tuple : table.getAllTuples()) {
				ITuple newTuple = new Tuple();
				for (ITupleElement tupleElement : tuple.getAllElements()) {
					ITupleElement teClone = (ITupleElement)tupleElement.clone();
					teClone.setColumnName(table.getName() + "." + teClone.getColumnName());
					newTuple.addTupleElement(teClone);
				}
				joinTable.addTuple(newTuple);
			}
		}
		
		// Now that we have the new column defintions, we have to update the tuple column defintions
		// and add in missing columns
		// CAUTION: O(n^3)
		for (ITuple tuple : joinTable.getAllTuples()) {
			for (IColumnDefinition def : joinTable.getColumnLists()) {
				boolean hasColumn = false;
				for (ITupleElement elem : tuple.getAllElements()) {
					if (elem.getColumnName().equals(def.getColumnName())) {
						hasColumn = true;
						break;
					}
				}
				
				// Insert a column with a value of null if the column didn't previously exist
				if (!hasColumn) {
					tuple.addTupleElement(def.createEmptyTupleElement());
				}
			}
		}
		
		return joinTable;
	}
	
	/**
	 * Cleans up the name of a column or table from a query
	 * 
	 * This will strip any spaces around the column name as well as any commas
	 * 
	 * @param name
	 * @return Cleaned up name
	 * @throws Exception when a case that cannot be handled is encountered
	 */
	protected String trimQueryName(String name) throws Exception {
		// Trim it first
		name = name.trim();
		
		while (name.contains(",")) {
			// Check for the comma at the beginning, then end of the string. 
			if (name.indexOf(",") == 0) {
				name = name.substring(1);
			} else if (name.indexOf(",") == name.length()-1) {
				name = name.substring(0, name.length()-1);
			} else if (name.indexOf(",") > 0 && name.indexOf(",") < name.length()-1) {
				throw new Exception("Cannot handle cases where commas are in middle of string.");
			}
		}
		
		// Trim it again before returning
		return name.trim();
	}
	
	/**
	 * Parses the query string and returns an array of sub-strings for further processing
	 * 
	 * This method was developed to help refactor and more easily test the individual
	 * components of of the Query Parser. It will return an array containing 4 elements
	 * (0-indexed) with the matching (or nil) results of the regex parsing. 
	 * 
	 * @param query
	 * @return Array of 4 string elements with results from the regex parsing
	 * @throws SqlException when the select query cannot be parsed
	 */
	protected String[] parseQueryString(String query) throws SqlException {
		String[] parseResults = new String[4];
		try {
			// Pattern must begin with SELECT, have either * or a list of columns
			// separated by commas, the word FROM and either a single table or multiple
			// tables separated by commas. Finally, an optional where condition will exist
			// GROUP 1 == Column List
			// GROUP 2 == Table List
			// GROUP 4 == Filter Conditions
			// Group 1 should match the following cases:
			//  - *
			//  - columnCaseInsensitive23
			//  - 234otherColumns AS otherNames93
			Pattern queryLine = Pattern.compile("^SELECT[ ]+([\\.,\\(\\) a-zA-Z0-9*]+)[ ]+FROM[ ]+([a-zA-Z0-9]+)[ ]*(,[ ]*[a-zA-Z0-9]+)?([ ]*WHERE[ ]*)?(.*)$");
			Matcher queryMatch = queryLine.matcher(query);
			if (!queryMatch.matches()) {
				throw new SqlException("Unable to parse select query.");
			}
			
			// Pick up the columns requested
			parseResults[0] = queryMatch.group(1);
			
			// Pick up the first table
			parseResults[1] = this.trimQueryName(queryMatch.group(2));
			
			// Pick up the additional join table (optional)
			if (queryMatch.group(3) != null && queryMatch.group(3) != "") {
				parseResults[2] = this.trimQueryName(queryMatch.group(3));
			} else {
				parseResults[2] = "";
			}
			
			// Pick up the final conditions if where is present
			if (queryMatch.group(4) != null && queryMatch.group(4) != "" && 
					queryMatch.group(4).toUpperCase().contains("WHERE") && queryMatch.group(5) != "") {
				parseResults[3] = queryMatch.group(5);
			} else {
				parseResults[3] = "";
			}
		} catch (Exception e) {
			throw new SqlException("Unable to parse select query.", e);
		}
			
		return parseResults;
	}
	
	/**
	 * Reduces the view passed in to contain only the columns requested in the query
	 * 
	 * @param columns Columns to pull out
	 * @param view Current view containing all columns.
	 * @return View with only the requested columns
	 * @throws Exception
	 */
	protected IDatabaseTable reduceToColumnsNecessary(String columns, IDatabaseTable view) throws Exception {
		// Cast the DatabaseTable to a DatabaseTable object to be able to clone the object
		Table localTable = (Table)view;
		IDatabaseTable newView = (IDatabaseTable)localTable.clone();
		if (newView == null) {
			throw new Exception("Cannot clone the view.");
		}
		
		/*
		 * Return everything in the view if all columns requested
		 */
		if (columns.trim().equals("*")) {
			return newView;
		}
		
		List<String> newColumnList = null;
		try {
			// Get list of columns and clean them up
			String[] columnList = columns.split(",");
			newColumnList = new ArrayList<String>();
			for (String column : columnList) {
				newColumnList.add(this.trimQueryName(column));
			}
		} catch (Exception e) {
			throw e;
		}
		
		// Check for final operators, such as COUNT, SUM, MIN, and MAX
		List<String> afterOperatorsColumnList = new ArrayList<String>();
		for (String columnName : newColumnList) {
			if (columnName.toUpperCase().contains("COUNT(") ||
					columnName.toUpperCase().contains("SUM(") ||
					columnName.toUpperCase().contains("MIN(") ||
					columnName.toUpperCase().contains("MAX(")) {
				if (!columnName.toUpperCase().contains(" AS ")) {
					throw new Exception("alias statement is missing from column operator.");
				}
				// Store the aliased column name
				String[] columnSplit = columnName.split("[ ]+[aA][sS][ ]+");
				afterOperatorsColumnList.add(columnSplit[1]);
				
				// Figure out what operator to call and call it!
				Pattern operatorLine = Pattern.compile("^([A-Z]*)\\((.*)\\)$");
				Matcher operatorMatch = operatorLine.matcher(columnSplit[0].trim());
				if (!operatorMatch.matches()) {
					throw new Exception("The operator doesn't match expected syntax.");
				}
				String operation = operatorMatch.group(1);
				String origColumnName = operatorMatch.group(2);
				
				// Perform the operation on the group
				if (operation.equals("COUNT")) {
					newView = CountTable(newView, columnSplit[1]);
				} else if (operation.equals("SUM")) {
					newView = SumTable(newView, origColumnName, columnSplit[1]);
				} else if (operation.equals("MIN")) {
					newView = TableMinColumn(newView, origColumnName, columnSplit[1]);
				} else if (operation.equals("MAX")) {
					newView = TableMaxColumn(newView, origColumnName, columnSplit[1]);
				}
			} else {
				// Add the columnName to the next column list for processing
				afterOperatorsColumnList.add(columnName);
			}
		}
		
		// Check for ALIASING
		List<String> aliasedColumnList = new ArrayList<String>();
		for (String columnName : afterOperatorsColumnList) {
			if (columnName.toUpperCase().contains(" AS ")) {
				// Split all spaces surrounding AS and for whatever case
				String[] columnSplit = columnName.split("[ ]+[aA][sS][ ]+");
				// Update the list with the new name
				aliasedColumnList.add(columnSplit[1]);
				for (IColumnDefinition def : newView.getColumnLists()) {
					if (def.getColumnName().equals(columnSplit[0])) {
						// Remove the object first, update the name and re-add it
						int position = newView.getColumnLists().indexOf(def);
						newView.getColumnLists().remove(def);
						// Update the column name
						def.setColumnName(columnSplit[1]);
						newView.getColumnLists().add(position, def);
						
						break;
					}
				}
				// Now rename the columns for all tuples
				for (ITuple tuple : newView.getAllTuples()) {
					for (ITupleElement te : tuple.getAllElements()) {
						if (te.getColumnName().equals(columnSplit[0])) {
							// Remove the object first, update the name and re-add it
							int position = tuple.getAllElements().indexOf(te);
							tuple.getAllElements().remove(te);
							te.setColumnName(columnSplit[1]);
							tuple.getAllElements().add(position, te);
							
							break;
						}
					}
				}
			} else {
				// Add the column to the retention list
				aliasedColumnList.add(columnName);
			}
		}
		
		// Iterate over the column definitions in the current view and see if they match up
		// Delete the columns (which will delete the columns in the tuples) if they don't
		// NOTE: Can't run deleteColumn in the for loop else ConcurrentModificationException
		// will occur.
		List<String> colsToDelete = new ArrayList<String>();
		for (IColumnDefinition column : newView.getColumnLists()) {
			if (!aliasedColumnList.contains(column.getColumnName())) {
				colsToDelete.add(column.getColumnName());
			}
		}
		for (String col : colsToDelete) {
			newView.deleteColumn(col);
		}
		
		return newView;
	}
	
	/**
	 * Default QueryParser constructor
	 */
	public QueryParser() {
	}
	
	/**
	 * QueryParser constructor that takes a DatabaseTable parameter
	 * 
	 * Because the queries in this parser operate on a DatabaseTable, we might
	 * want to provide the DatabaseTable to parse on construction. 
	 * 
	 * @param table DatabaseTable query will be performed on
	 */
	public QueryParser(List<IDatabaseTable> tables) {
		this._tables = tables;
	}
	
	/**
	 * Retrieves the database table this parser operates on
	 * 
	 * @return DatabaseTable object this parser operates on
	 */
	public List<IDatabaseTable> getDatabaseTables() {
		return this._tables;
	}
	
	/**
	 * Sets the database table this parser should operate on
	 * 
	 * @param table DatabaseTable this parser should operate on
	 */
	public void setDatabaseTables(List<IDatabaseTable> tables) {
		this._tables = tables;
	}
	
	/**
	 * Performs a select query against a database table
	 * 
	 * @param String query Query string to parse
	 * @return Database View containing matching records from the filter
	 * @throws NullReferenceException, SqlException
	 * @todo Implement more complex selection critera, including AS, JOINS, etc.
	 */
	public IDatabaseTable selectQuery(String query) throws NullReferenceException, SqlException {
		if (this._tables == null) {
			throw new NullReferenceException("No database tables to operate on.");
		}
		
		IDatabaseTable returnView = null;
		// Implement the filter against the database table and return the results
		try {
			String[] queryResults = this.parseQueryString(query);
			String tableName = this.trimQueryName(queryResults[1]);
			String joinName = "";
			if (queryResults[2] != "") {
				joinName = queryResults[2];
			}
			// Fetch the list of table names to filter on and create a joined table
			List<IDatabaseTable> tablesToJoin = new ArrayList<IDatabaseTable>();
			for (IDatabaseTable table : this._tables) {
				if (table.getName().equals(tableName)) {
					tablesToJoin.add(table);
				} else if (joinName != "" && table.getName().equals(joinName)) {
					tablesToJoin.add(table);
				}
			}
			returnView = this.createJoinedTable(tablesToJoin);
			QueryFilter filter = new QueryFilter(returnView);
			filter.setAllTables(this._tables);
			
			// Add the where clause to the filter for processing, if set
			if (queryResults[3] != "") {
				filter.applyFilter(queryResults[3]);
			}
			
			// Get the results
			List<ITuple> tuples = filter.fetchResults();
			
			// Clear the tuples and put the results into the join table
			returnView.clearTuples();
			for (ITuple tuple : tuples) {
				returnView.addTuple(tuple);
			}
			
			// Reduce the view down to only the columns requested
			returnView = this.reduceToColumnsNecessary(queryResults[0], returnView);
		} catch (SqlException e) {
			throw e;
		} catch (NullReferenceException e) {
			throw e;
		} catch (Exception e) {
			throw new SqlException("An unhandled exception has occurred.");
		}
		
		return returnView;
	}
	
	/**
	 * Deletes records in tables based on the query
	 * 
	 * The query should be in the same format that would be seen after a WHERE
	 * statement in a SELECT query. In other words, it should be similar to:
	 * 
	 *  - id = 1
	 *  - ((id = 1) OR (id = 2))
	 *  - id < 5
	 *  - etc. 
	 *  
	 * It should not be a full-fleged SQL statement such as DELETE FROM TABLE. 
	 * However, if "" is passed into query, then all records will be obtained
	 * from the filter, leading to the deletion of all records from the table.
	 * 
	 * @param 	query
	 * @todo	Implement the delete query
	 */
	public void deleteQuery(IDatabaseTable tableName, String query) 
		throws NullReferenceException, SqlException {
		if (this._tables == null) {
			throw new NullReferenceException("No database tables to operate on.");
		}
		
		try {
			// Find the table this operates on
			IDatabaseTable table = null;
			for (IDatabaseTable tbl : this._tables) {
				if (tbl.getName().equals(tableName)) {
					table = tbl;
					break;
				}
			}
			// If we have no table to filter on, throw an exception
			if (table == null) {
				throw new SqlException("Table " + tableName + " could not be found.");
			}
			QueryFilter filter = new QueryFilter(table);
			filter.setAllTables(this._tables);
			
			// Add the where clause to the filter for processing, if set
			filter.applyFilter(query);
			
			// Get the results
			List<ITuple> tuples = filter.fetchResults();
			
			// Delete the records
			for (ITuple tupleToDel : tuples) {
				table.deleteTuple(tupleToDel);
			}
		} catch (SqlException e) {
			throw e;
		} catch (NullReferenceException e) {
			throw e;
		} catch (Exception e) {
			throw new SqlException("An unhandled exception has occurred.");
		}
	}
}	